﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestXNA3.Actors;
using System.IO;
using Microsoft.Xna.Framework;
using TestXNA3.Resources.Meshes;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

namespace TestXNA3.Generic
{
    [Serializable]
    public class Map : ISerializable
    {
        [Serializable]
        private struct ScaledStaticMeshDesc
        {
            public string MeshName;
            public Vector3 Scale;
            public MemoryStream ConvexMeshStream;
            public MemoryStream TriMeshStream;
            [NonSerialized]
            public StillDesign.PhysX.ConvexMesh ConvexMesh;
            [NonSerialized]
            public StillDesign.PhysX.TriangleMesh TriMesh;
        }
        private List<ScaledStaticMeshDesc> _scaledStaticMeshes = new List<ScaledStaticMeshDesc>();

        public string Name;
        public Scene MainScene;

        public Map(Scene mainScene)
        {
            Name = "Untitled";
            MainScene = mainScene;
        }

        protected Map(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new System.ArgumentNullException("info");
        }

        public void Load(string filename)
        {
            FileStream stream = new FileStream("Content\\Maps\\" + filename, FileMode.Open);
            BinaryReader reader = new BinaryReader(stream);

            Name = reader.ReadString();
            MainScene.Load(reader);

            long totalScaledMeshes = reader.ReadInt64();
            for (int i = 0; i < totalScaledMeshes; i++)
            {
                ScaledStaticMeshDesc scaledStaticMeshDesc = new ScaledStaticMeshDesc();
                scaledStaticMeshDesc.MeshName = reader.ReadString();

                Vector3 scale = Vector3.One;
                scale.X = reader.ReadSingle();
                scale.Y = reader.ReadSingle();
                scale.Z = reader.ReadSingle();
                scaledStaticMeshDesc.Scale = scale;

                bool haveConvex = reader.ReadBoolean();
                bool haveTriMesh = reader.ReadBoolean();

                if (haveConvex)
                {
                    long convexStreamLength = reader.ReadInt64();
                    MemoryStream convexStream = new MemoryStream((int)convexStreamLength);
                    byte[] bytes = convexStream.GetBuffer();
                    reader.Read(bytes, 0, (int)convexStreamLength);
                    convexStream.Write(bytes, 0, (int)convexStreamLength);
                    convexStream.Position = 0;
                    scaledStaticMeshDesc.ConvexMesh = GameStartup.PhysXCore.CreateConvexMesh(convexStream);
                    scaledStaticMeshDesc.ConvexMeshStream = convexStream;
                }
                if (haveTriMesh)
                {
                    long triStreamLength = reader.ReadInt64();
                    MemoryStream triStream = new MemoryStream((int)triStreamLength);
                    byte[] bytes = triStream.GetBuffer();
                    reader.Read(bytes, 0, (int)triStreamLength);
                    triStream.Write(bytes, 0, (int)triStreamLength);
                    triStream.Position = 0;
                    scaledStaticMeshDesc.TriMesh = GameStartup.PhysXCore.CreateTriangleMesh(triStream);
                    scaledStaticMeshDesc.TriMeshStream = triStream;
                }
                _scaledStaticMeshes.Add(scaledStaticMeshDesc);
            }

            reader.Close();
            stream.Close();

            MainScene.MapFullyLoaded(this);
        }

        public void Save(string filename)
        {
            FileStream stream = null;
            BinaryWriter writer = null;
            try
            {
                stream = new FileStream(TestConfig.ContentPath + "Maps\\" + filename, FileMode.Create);
                if (stream != null)
                    writer = new BinaryWriter(stream);
            }
            catch
            {
                Logger.Print("Can't save map " + filename);
            }

            if (stream == null || writer == null)
                return;

            writer.Write(Name);
            MainScene.Save(writer);

            long totalScaledMeshes = _scaledStaticMeshes.Count;
            writer.Write(totalScaledMeshes);
            for (int i = 0; i < totalScaledMeshes; i++)
            {
                bool haveConvex = _scaledStaticMeshes[i].ConvexMeshStream != null;
                bool haveTriMesh = _scaledStaticMeshes[i].TriMeshStream != null;
                writer.Write(_scaledStaticMeshes[i].MeshName);
                writer.Write(_scaledStaticMeshes[i].Scale.X);
                writer.Write(_scaledStaticMeshes[i].Scale.Y);
                writer.Write(_scaledStaticMeshes[i].Scale.Z);
                writer.Write(haveConvex);
                writer.Write(haveTriMesh);
                if (haveConvex)
                {
                    long convexMeshStreamLength = _scaledStaticMeshes[i].ConvexMeshStream.Length;
                    writer.Write(convexMeshStreamLength);
                    writer.Write(_scaledStaticMeshes[i].ConvexMeshStream.GetBuffer());
                }
                if (haveTriMesh)
                {
                    long triMeshStreamLength = _scaledStaticMeshes[i].TriMeshStream.Length;
                    writer.Write(triMeshStreamLength);
                    writer.Write(_scaledStaticMeshes[i].TriMeshStream.GetBuffer());
                }
            }

            writer.Close();
            stream.Close();

            try
            {
                stream = new FileStream(TestConfig.ContentPath + "Maps\\" + filename + ".ss", FileMode.Create);
                if (stream != null)
                    writer = new BinaryWriter(stream);
            }
            catch
            {
                Logger.Print("Can't save .ss map " + filename);
            }

            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, this);
            stream.Close();
        }

        public void AddScaledStaticMesh(StaticMesh staticMesh, Vector3 scale, StaticMeshActor.EPhysicsMode physMode)
        {
            if (!(physMode == StaticMeshActor.EPhysicsMode.DynamicConvex || physMode == StaticMeshActor.EPhysicsMode.Static))
                return;

            for (int i = 0; i < _scaledStaticMeshes.Count; i++)
            {
                if (_scaledStaticMeshes[i].MeshName == staticMesh.MeshName)
                {
                    if ((_scaledStaticMeshes[i].Scale - scale).Length() < 0.1f)
                    {
                        return;
                    }
                }
            }

            ScaledStaticMeshDesc scaledStaticMeshDesc = new ScaledStaticMeshDesc();

            scaledStaticMeshDesc.MeshName = staticMesh.MeshName;
            scaledStaticMeshDesc.Scale = scale;

            Matrix scaleMatrix = Matrix.CreateScale(scale);
            ModelImporter.ModelTestDesc.Triangle[] triangles = staticMesh.TraceTriangles;
            int[] indices = new int[triangles.Length * 3];
            Vector3[] vertices = new Vector3[triangles.Length * 3];

            // Build scaled mesh
            int idx = 0;
            for (int i = 0; i < triangles.Length; i++)
            {
                indices[idx + 0] = idx + 0;
                indices[idx + 1] = idx + 1;
                indices[idx + 2] = idx + 2;
                vertices[idx + 0] = Vector3.Transform(triangles[i].p0, scaleMatrix);
                vertices[idx + 1] = Vector3.Transform(triangles[i].p1, scaleMatrix);
                vertices[idx + 2] = Vector3.Transform(triangles[i].p2, scaleMatrix);
                idx += 3;
            }

            // Build tri mesh
            if (physMode == StaticMeshActor.EPhysicsMode.Static)
            {
                StillDesign.PhysX.TriangleMeshDescription triangleMeshDesc = new StillDesign.PhysX.TriangleMeshDescription();

                triangleMeshDesc.TriangleCount = indices.Length / 3;
                triangleMeshDesc.VertexCount = vertices.Length;

                triangleMeshDesc.Flags = StillDesign.PhysX.MeshFlag.FlipNormals;

                triangleMeshDesc.AllocateTriangles<int>(triangleMeshDesc.TriangleCount);
                triangleMeshDesc.AllocateVertices<Vector3>(triangleMeshDesc.VertexCount);

                triangleMeshDesc.TriangleStream.SetData(indices);
                triangleMeshDesc.VerticesStream.SetData(vertices);

                try
                {
                    MemoryStream meshStream = new MemoryStream();
                    
                    StillDesign.PhysX.Cooking.InitializeCooking();
                    StillDesign.PhysX.Cooking.CookTriangleMesh(triangleMeshDesc, meshStream);
                    StillDesign.PhysX.Cooking.CloseCooking();

                    meshStream.Position = 0;
                    scaledStaticMeshDesc.TriMesh = GameStartup.PhysXCore.CreateTriangleMesh(meshStream);
                    scaledStaticMeshDesc.TriMeshStream = meshStream;
                }
                catch
                {
                    return;
                }
            }
            if (physMode == StaticMeshActor.EPhysicsMode.DynamicConvex)
            {

                StillDesign.PhysX.ConvexMeshDescription convexMeshDesc = new StillDesign.PhysX.ConvexMeshDescription();
                convexMeshDesc.PointCount = vertices.Length;
                convexMeshDesc.Flags = StillDesign.PhysX.ConvexFlag.ComputeConvex | StillDesign.PhysX.ConvexFlag.InflateConvex;

                convexMeshDesc.AllocatePoints<Vector3>(convexMeshDesc.PointCount);
                for (int j = 0; j < convexMeshDesc.PointCount; j++)
                {
                    convexMeshDesc.PointsStream.Write(vertices[j]);
                }

                try
                {
                    MemoryStream convexStream = new MemoryStream();
                    StillDesign.PhysX.Cooking.InitializeCooking();
                    StillDesign.PhysX.Cooking.CookConvexMesh(convexMeshDesc, convexStream);
                    convexStream.Position = 0;
                    scaledStaticMeshDesc.ConvexMeshStream = convexStream;
                    scaledStaticMeshDesc.ConvexMesh = GameStartup.PhysXCore.CreateConvexMesh(convexStream);
                }
                catch
                {
                }
            }
            _scaledStaticMeshes.Add(scaledStaticMeshDesc);
        }

        public void FindScaledStaticMesh(StaticMesh staticMesh, Vector3 scale, StaticMeshActor.EPhysicsMode physMode
            , out StillDesign.PhysX.TriangleMesh meshShape, out StillDesign.PhysX.ConvexMesh convexShape)
        {
            convexShape = null;
            meshShape = null;

            if (!(physMode == StaticMeshActor.EPhysicsMode.DynamicConvex || physMode == StaticMeshActor.EPhysicsMode.Static))
                return;

            for (int i = 0; i < _scaledStaticMeshes.Count; i++)
            {
                if (_scaledStaticMeshes[i].MeshName == staticMesh.MeshName)
                {
                    if ((_scaledStaticMeshes[i].Scale - scale).Length() < 0.1f)
                    {
                        convexShape = _scaledStaticMeshes[i].ConvexMesh;
                        meshShape = _scaledStaticMeshes[i].TriMesh;
                        return;
                    }
                }
            }
            AddScaledStaticMesh(staticMesh, scale, physMode);
            FindScaledStaticMesh(staticMesh, scale, physMode, out meshShape, out convexShape);
        }

        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Name", Name);
            info.AddValue("_scaledStaticMeshes", _scaledStaticMeshes);
            info.AddValue("MainScene", MainScene);
        }
    }
}
